package com.yuneec.rcService.impl;

import android.os.SystemClock;

import com.yuneec.droneservice.ConnectionListener;
import com.yuneec.droneservice.ServiceManager;
import com.yuneec.droneservice.command.CommandListener;
import com.yuneec.droneservice.command.GetControllerType;
import com.yuneec.droneservice.command.GetControllerVersion;
import com.yuneec.droneservice.command.MAV_RC_ERROR_CODE;
import com.yuneec.droneservice.command.SendMd5;
import com.yuneec.droneservice.command.StartUpdate;
import com.yuneec.droneservice.command.TransFileData;
import com.yuneec.droneservice.model.ControllerVersion;
import com.yuneec.droneservice.model.PackageInfo;
import com.yuneec.droneservice.request.IResponse;
import com.yuneec.droneservice.request.OnRequestListener;
import com.yuneec.droneservice.tools.RCLog;
import com.yuneec.droneservice.tools.SerialCommand;
import com.yuneec.rcService.IRCOTA;
import com.yuneec.rcService.RcCallBack;
import com.yuneec.rcService.RcCallBack.RCC;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;

public class RCOTAImpl implements IRCOTA {
    private File updateFile;
    private String md5;

    @Override
    public void addConnectionListener(ConnectionListener connectionListener) {
        ServiceManager.getInstance().addConnectionListener(connectionListener);
    }

    @Override
    public void removeConnectionListener(ConnectionListener connectionListener) {
        ServiceManager.getInstance().removeConnectionListener(connectionListener);
    }

    @Override
    public boolean isConnected() {
        return ServiceManager.getInstance().isConnected();
    }

    @Override
    public void getRCModel(RcCallBack<String> callback) {
        ServiceManager.getInstance().sendDroneCommand(new GetControllerType(), new RCC(callback));
    }

    @Override
    public void getRCVersion(RcCallBack<ControllerVersion> callback) {
        ServiceManager.getInstance().sendDroneCommand(new GetControllerVersion(), new RCC(callback));
    }


    @Override
    public void uploadUpgradeFile(File file, final RcCallBack<Void> callback) {
        if (file == null || callback == null) {
            throw new RuntimeException("updateFile and callBack can not be null!!!");
        }
        updateFile = file;
        final PackageInfo packageInfo = new PackageInfo();
        packageInfo.setFileSize(file.length());
        final SerialCommand.INextRequest iNextStartUpdateRequest = new SerialCommand.INextRequest();
        SerialCommand.newInstance().force(false).next(new GetControllerType(), new OnRequestListener<IResponse<String>>() {
            @Override
            public void onSuccess(IResponse<String> response) {
                RCLog.d("GetControllerType.onSuccess");
                if (response.getCode() == MAV_RC_ERROR_CODE.MAV_RC_ERR_SUCCESS) {
                    packageInfo.setType(response.getParam());
                } else {
                    throw new RuntimeException("GetControllerType error:" + response.getCode());
                }

            }
        }).next(new GetControllerVersion(), new OnRequestListener<IResponse<ControllerVersion>>() {
            @Override
            public void onSuccess(IResponse<ControllerVersion> response) {
                RCLog.d("GetControllerVersion.onSuccess");
                ControllerVersion version = null;
                if (response == null || response.getCode() != MAV_RC_ERROR_CODE.MAV_RC_ERR_SUCCESS || (version = response.getParam()) == null) {
                    throw new RuntimeException("GetControllerVersion error:" + response.getCode());
                }
                RCLog.d("controller version:" + version.toString());
                packageInfo.setMcuVersion(version.getMcuVersion());
                packageInfo.setFirmwareVersion(version.getFirmwareVersion());
                packageInfo.setHardwareVersion(version.getHardwareVersion());
                packageInfo.setFilename("update.lzo");
                iNextStartUpdateRequest.setRequest(new StartUpdate(packageInfo));
            }
        }).next(iNextStartUpdateRequest, new OnRequestListener() {
            @Override
            public void onSuccess(IResponse response) {
                RCLog.d("StartUpdate.onSuccess");
            }

        }).onFinished(new RCC<Void>(callback) {
            @Override
            public void onSuccess(IResponse<Void> response) {
                RCLog.d("onFinished.onSuccess");
                md5 = getFileMD5String(updateFile);
                new SendFileCB(updateFile, callback).start();
            }

            @Override
            public void onError(int errorCode) {
                RCLog.d("onFinished...onError:" + errorCode);
                updateFile = null;
                md5 = null;
                callback.onFail();
            }

            @Override
            public void onTimeout() {
                RCLog.d("onFinished...onTimeout");
                updateFile = null;
                md5 = null;
                callback.onTimeout();
            }
        }).request();
    }

    private static class SendFileCB extends CommandListener<Void> {
        private FileInputStream fis;
        private int seq;
        private byte[] buf;
        private int len;
        private int tryCount = 0;
        private RcCallBack<Void> cb;

        SendFileCB(File file, RcCallBack<Void> cb) {
            try {
                RCLog.d("路径：" + file.getAbsolutePath() + "\t文件总大小:" + file.length());
                fis = new FileInputStream(file);
            } catch (FileNotFoundException e) {
                RCLog.e("文件未找到...", e);
                fis = null;
            }
            buf = new byte[3500];
            this.cb = cb;
        }

        private void recycle() {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                }
            }
            cb = null;
        }

        public void start() {
            RCLog.d("开始上传升级文件...");
            if (fis == null) {
                RCLog.d("文件流读取失败...");
                cb.onFail();
                recycle();
            } else {
                sendBuf(false);
            }
        }

        private void sendBuf(boolean retry) {
            try {
                if (retry) {
                    RCLog.d("上次发送失败,重发...");
                    ServiceManager.getInstance().sendDroneCommand(new TransFileData(seq, buf, 0, len), SendFileCB.this);
                } else {
                    len = fis.read(buf);
                    if (len > 0) {
                        ServiceManager.getInstance().sendDroneCommand(new TransFileData(seq, buf, 0, len), SendFileCB.this);
                        RCLog.d(String.format("发送:%d\tseq:%d", len, seq));
                    } else {
                        RCLog.d("发送完成!");
                        cb.onSuccess(null);
                        recycle();
                    }
                }
            } catch (Exception e) {
                cb.onFail();
                recycle();
            }
        }

        @Override
        public void onSuccess(IResponse<Void> response) {
            RCLog.d("send UpdateFile onSuccess:" + response.getCode());
            int resCode = MAV_RC_ERROR_CODE.MAV_RC_ERR_UNKNOW;
            if (response == null || (resCode = response.getCode()) != MAV_RC_ERROR_CODE.MAV_RC_ERR_SUCCESS) {
                onError(resCode);
            } else {
                tryCount = 0;
                seq++;
                sendBuf(false);
            }
        }

        @Override
        public void onTimeout() {
            onError(MAV_RC_ERROR_CODE.TIME_OUT);
        }

        @Override
        public void onError(int errorCode) {
            RCLog.d(String.format("send UpdateFile error:%d,trycount:%d", errorCode, tryCount));
            if (tryCount >= 3) {
                //失败
                cb.onFail();
                recycle();
            } else {
                tryCount++;
                sendBuf(true);
            }
        }
    }

    @Override
    public void startUpgrade(final RcCallBack<ControllerVersion> callback) {
        updateFile = null;
        ServiceManager.getInstance().sendDroneCommand(new SendMd5(md5), new OnRequestListener() {
            @Override
            public void onSuccess(IResponse response) {
                super.onSuccess(response);
                RCLog.d("start updating....");
                SystemClock.sleep(5000);
                ServiceManager.getInstance().sendDroneCommand(new GetControllerVersion(), new OnRequestListener<IResponse<ControllerVersion>>() {
                    @Override
                    public void onSuccess(IResponse<ControllerVersion> response) {
                        if (response.getCode() == MAV_RC_ERROR_CODE.MAV_RC_ERR_SUCCESS) {
                            callback.onSuccess(response.getParam());
                        } else {
                            callback.onFail();
                        }
                    }

                    @Override
                    public void onTimeout() {
                        onError(MAV_RC_ERROR_CODE.TIME_OUT);
                    }

                    @Override
                    public void onError(int errorCode) {
                        super.onError(errorCode);
                        SystemClock.sleep(2000);
                        ServiceManager.getInstance().sendDroneCommand(new GetControllerVersion(), this);
                    }

                });
            }

            @Override
            public void onTimeout() {
                callback.onTimeout();
            }

            @Override
            public void onError(int errorCode) {
                callback.onFail();
            }
        });
    }


    private static String getFileMD5String(File file) {
        if (file == null) {
            return "";
        }
        MessageDigest digest = null;
        FileInputStream in = null;
        byte buffer[] = new byte[1024];
        int len;
        try {
            digest = MessageDigest.getInstance("MD5");
            in = new FileInputStream(file);
            while ((len = in.read(buffer, 0, 1024)) != -1) {
                digest.update(buffer, 0, len);
            }
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
        BigInteger bigInt = new BigInteger(1, digest.digest());
        return bigInt.toString(16);
    }
}
